const fs = require("fs");
// 引入自定义的日志模块
const logger = require("./logger");
// 引入子进程模块，用于创建子进程
const { spawn } = require("child_process");

// 设置环境变量，指定编码为 UTF-8
//const env = { ...process.env, LANG: "UTF-8" };
let timeoutTimer = undefined;

// 使用子进程执行命令
// 定义 runCmd 函数，参数包括：
// cmd: 要执行的命令
// args: 命令的参数数组
// callback: 命令执行完成后的回调函数
// socketIo: 一个可选的 Socket.IO 实例，用于实时通信
// msgTag: 消息标签，默认为 "common-msg"
// timeoutMinute: 命令执行的超时时间，默认为 2 分钟
function runCmd(
  cmd,
  args,
  callback,
  socketIo,
  msgTag = "common-msg",
  timeoutMinute = 2
) {
  console.log("检测当前运行平台====>", process.platform);

  console.log("命令====>", cmd);
  console.log("参数====>", args);

  // 使用 spawn 方法创建子进程执行命令
  const child = spawn(cmd, args, {
    //env: env, // 使用自定义环境变量
    windowsHide: true, // 隐藏子进程控制台窗口
    shell: process.platform === "win32", // 仅在当前运行环境为 Windows 时，才使用 shell
  }); // sh xxx.sh

  socketIo && socketIo.emit(msgTag, `[system] runCmd: ${args[0]}, ${msgTag}`);
  socketIo &&
    socketIo.emit(msgTag, `[system] 超时限制：${timeoutMinute} 分钟;`);

  let resp = "当前执行路径：" + process.cwd() + "\n";
  logger.info(resp);
  socketIo && socketIo.emit(msgTag, resp);
  let shellCmd = cmd + " " + args + "\n";

  // 如果参数数组的第一个元素包含 ".sh"，则读取该脚本文件的内容
  let shellText = args[0].includes(".sh")
    ? fs.readFileSync(args[0] || "").toString() // 读取文件内容并转换为字符串
    : ""; // 如果不是 .sh 文件，则 shellText 为空字符串

  socketIo && socketIo.emit(msgTag, `开始执行脚本： ${shellCmd}`);
  socketIo && socketIo.emit(msgTag, `--------------`);
  socketIo && socketIo.emit(msgTag, shellText);
  socketIo && socketIo.emit(msgTag, `--------------`);
  socketIo &&
    socketIo.emit(
      msgTag,
      `>>> [system] 子进程 : pid ====> 【${child.pid}】, 正在运行中....`
    );
  let startTime = +new Date();
  let dataFunc = (buffer) => {
    if (child.killed) {
      // 如果已结束
      callback("运行超时，已停止", "isError");
      timeoutTimer && clearTimeout(timeoutTimer);
      socketIo &&
        socketIo.emit(msgTag, `[system] ${child.pid} child.killed, 结束`);
      // fix 进程被 kill 后，还在不断接收数据问题（使用 pm2 log 测试得出该结论）
      child.stdout.off("data", dataFunc);
      return;
    }
    let info = buffer.toString();

    info = `${new Date().toLocaleString()}: ${info}`;
    resp += info;
    logger.info("读取子进程标准输出=======>", info);

    socketIo && socketIo.emit(msgTag, "【输出结果】:" + info);

    // console.log(child);
    // log 较多时，怎么实时将消息通过接口返给前端，只能是 socket ？
    // 除了 socket 怎么将 log 数据一点点通过接口传给前端
  };
  child.stdout.on("data", dataFunc);
  child.stdout.on("end", function () {
    console.log(">>stdout end");
    callback(resp);
    timeoutTimer && clearTimeout(timeoutTimer);
    // socketIo && socketIo.emit(msgTag, `[system] child stdout end`);
  });

  // shell 脚本执行错误信息也返回
  // let errorMsg = ""; // 错误信息 end、正常信息 end 可能有先后，统一成一个信息
  child.stderr.on("data", (buffer) => {
    let info = buffer.toString();
    info = `${new Date().toLocaleString()}: ${info}`;
    resp += info;
    logger.info("读取子进程标准错误输出 =======>", info);
    socketIo && socketIo.emit(msgTag, "输出结果 : " + info);
  });
  child.stderr.on("end", function () {
    console.log(">>err end");
    callback(resp);
    timeoutTimer && clearTimeout(timeoutTimer);
    // socketIo && socketIo.emit(msgTag, `[system] child stderr end`);
  });

  child.on("close", (code) => {
    console.log(
      "子进程退出 code :【" + code + "】 ======>> close",
      code == 1 ? "异常退出" : "正常退出"
    );
    if (code !== 0) {
      // let log = `[system] child 子进程非正常退出，ps process exited with code ${code}`;
      // socketIo && socketIo.emit(msgTag, log);
    }
    let useTime = (+new Date() - startTime) / 1000;
    socketIo &&
      socketIo.emit(
        msgTag,
        `>>> [system] 子进程 : pid ====> 【${child.pid}】  close 完成运行! 耗时：${useTime}s `
      );
    socketIo &&
      socketIo.emit(
        msgTag,
        `================================================================================`
      );
  });

  child.on("error", (err) => {
    console.error("执行命令失败:", err);
  });

  //默认超时两分钟
  const TIME_OUT_SEC = 1000 * 60 * timeoutMinute;
  timeoutTimer = setTimeout(() => {
    let log = `>>> [system]  ${child.pid} 执行超时 ${
      TIME_OUT_SEC / 1000
    }s，结束执行!`;
    socketIo && socketIo.emit(msgTag, log);
    child.kill();
  }, TIME_OUT_SEC);
}

module.exports = runCmd;
